section header vstart=0
    program_length      dd program_end
    code_entry          dw start 
                        dd section.code_1.start
    realloc_tbl_len     dw (header_end-code_1_segment)/4
    code_1_segment      dd section.code_1.start
    code_2_segment      dd section.code_2.start
    data_1_segment      dd section.data_1.start
    data_2_segment      dd section.data_2.start
    stack_segment       dd section.stack.start

    header_end:

SECTION code_1 align=16 vstart=0
put_string:
    mov cl,[bx]                                 ;把当前msg的值赋值给cl寄存器
    or cl,cl                                    ;检查cl的值是否为0
    jz .exit                                    ;是0，跳转到exit语句
    call put_char                               ;不是0，输出字符
    inc bx                                      ;递增到下一个字符
    jmp put_string                              ;反复输出字符

    .exit:
        ret                                     ;返回call调用

put_char:
    push ax
    push bx
    push cx
    push dx
    push ds
    push es                                     ;以上将寄存器存档

    mov dx,0x3d4                                ;0x3d4是显卡的寄存器索引端口，用来找显卡里面的东西用的
    mov ax,0x0e                                 ;0x0e是光标，赋值给ax
    out dx,al                                   ;向0x3d4端口写入0x0e(查找光标位置)
    mov dx,0x3d5                                ;0x3d5是显卡的数据端口，用来传输数据
    in al,dx                                    ;把0x0e处的光标数据写入al寄存器
    mov ah,al                                   ;光标的位置需要两个字节来表示，把当前位置放入ah寄存器

    mov dx,0x3d4                          
    mov al,0x0f
    out dx,al
    mov dx,0x3d5
    in al,dx                                    ;和上半部分一样的操作，只不过这里al存储光标位置的下半部分
    mov bx,ax                                   ;把光标位置放入bx寄存器

    cmp cl,0x0d                                 ;检查当前字符是否是回车                             
    jnz .put_0a                                 ;不是回车，就检查是否是换行符，跳转到put_0a
    mov ax,bx                                   ;是回车，跳到当前行的开头，把bx的值放到ax，方便计算
    mov bl,80                                   ;每行有80列字符，把80放到bl寄存器中
    div bl                                      ;整除80，得到当前光标在第几行（商）第几列（余数）
    mul bl                                      ;行号 × 每行字符数 → 光标对应 该行开头的位置
    mov bx,ax                                   ;把计算好的光标放回bx寄存器
    jmp .set_cursor                             ;设置光标寄存器的位置

.put_0a:
    cmp cl,0x0a                                  ;检查是否是换行符
    jnz .put_other                               ;不是0，代表不是换行符，跳转到输出其他字符
    add bx,80
    jmp .roll_screen

.put_other:
    mov ax,0xb800                               ;0xb800存放着显存地址，赋值给ax
    mov es,ax                                   ;把显存地址赋值给es寄存器
    shl bx,1                                    ;在显卡模式下，每个字符在现存中占用两个字节，所以bx得*2
    mov [es:bx],cl                              ;把当前字符放入到当前bx指向的显卡的位置es:bx

    shr bx,1                                    ;bx右移一位，除回去
    add bx,1                                    ;让光标显示到下一个位置

.roll_screen:
    cmp bx,2000                                 ;bx是光标的当前位置，为啥要和2000进行不叫，因为VGA的文本模式就是80列*25行=2000个字符，如果 bx < 2000：说明光标还在屏幕范围内，如果 bx >= 2000：说明光标已经跑到屏幕最后一行下面了，需要滚屏（roll screen）
                                                ;滚屏的意思就是把最上面一行去掉，然后下面出现新的一行
    jl .set_cursor                              ;bx比2000小，说明在屏幕内，跳转到set_cursor 

    push bx                                     ;等于或大于2000，先保存bx寄存器的值

    mov ax,0xb800                               ;0xb800存放着显存地址，赋值给ax
    mov ds,ax                                   ;把0xb800赋值给数据段寄存器
    mov es,ax                                   ;把显存地址赋值给es寄存器
    cld                                         ;将df位清零，使si和di寄存器递增
    mov si,0xa0                                 ;一行有80列*2个字节=160（0xa0）个字节，这里表示跳过第一行，从第二行的头开始
    mov di,0x00                                 ;这里指向屏幕数据的第一行
    mov cx,1920                                 ;1920 = 24行*80列，也就是除了第一行之外，剩下的 24 行字符数
    rep movsw                                   
    mov bx,3840                                 ;把第25行的起始地址赋值给bx，24行*160字节（80列，一列两个字符），
    mov cx,80                                   ;要把新上来的第25行清空
.cls:
    mov word[es:bx],0x0720                      ;0x0720会显示一个白色字符' '，背景黑色
    add bx,2                                    ;下一个字符
    loop .cls                                   ;不断循环，清空第25行

    pop bx                                      ;让bx寄存器恢复原值
    sub bx,80                                   ;一行有80列，让光标回到开头

.set_cursor:
    mov dx,0x3d4                                
    mov al,0x0e
    out dx,al                                   ;把0x0e写入索引端口，表示要操作光标的高字节
    mov dx,0x3d5
    mov al,bh                                   ;光标位置在bx寄存器，把光标位置的高字节(bh)写入al寄存器
    out dx,al                                   ;向显卡写入光标位置的高字节
    mov dx,0x3d4
    mov al,0x0f
    out dx,al                                   ;把0x0e写入索引端口，表示要操作光标的低字节
    mov dx,0x3d5
    mov al,bl
    out dx,al                                   ;向显卡写入光标位置的低字节
    ;这里整个函数的意思是把光标位置写入到VGA的光标寄存器里，这样就能知道下一个字符该出现在
    ;屏幕中的哪个格子了
    pop es
    pop ds
    pop dx
    pop cx
    pop bx
    pop ax
    ;恢复寄存器的原值
    ret

start:
    mov ax,[stack_segment]                  ;把当前栈段赋值给ax
    mov ss,ax                               ;让ss寄存器指向当前栈段
    mov sp,stack_end                        ;让sp指针指向栈顶

    mov ax,[data_1_segment]                 ;让ax寄存器指向data_1_segment段
    mov ds,ax                               ;让ds寄存器指向data_1_segment段

    mov bx,msg0                             ;让bx指向data_1_segment段的起始
    call put_string                         ;输出字符

    push word[es:code_2_segment]
    mov ax,begin
    push ax

    retf

continue:
    mov ax,[es:data_2_segment]
    mov ds,ax

    mov bx,msg1
    call put_string

    jmp $ 

SECTION code_2 align=16 vstart=0
begin:
    push word [es:code_1_segment]
    mov ax,continue
    push ax
    retf

SECTION data_1 align=16 vstart=0

    msg0 db '  This is NASM - the famous Netwide Assembler. '
         db 'Back at SourceForge and in intensive development! '
         db 'Get the current versions from http://www.nasm.us/.'
         db 0x0d,0x0a,0x0d,0x0a
         db '  Example code for calculate 1+2+...+1000:',0x0d,0x0a,0x0d,0x0a
         db '     xor dx,dx',0x0d,0x0a
         db '     xor ax,ax',0x0d,0x0a
         db '     xor cx,cx',0x0d,0x0a
         db '  @@:',0x0d,0x0a
         db '     inc cx',0x0d,0x0a
         db '     add ax,cx',0x0d,0x0a
         db '     adc dx,0',0x0d,0x0a
         db '     inc cx',0x0d,0x0a
         db '     cmp cx,1000',0x0d,0x0a
         db '     jle @@',0x0d,0x0a
         db '     ... ...(Some other codes)',0x0d,0x0a,0x0d,0x0a
         db 0

;===============================================================================
SECTION data_2 align=16 vstart=0

    msg1 db '  The above contents is written by LeeChung. '
         db '2011-05-06'
         db 0

;===============================================================================
SECTION stack align=16 vstart=0
           
         resb 256

stack_end:  

;===============================================================================
SECTION trail align=16
program_end: